package com.xiaoluo.lottery.service;

import com.alibaba.fastjson.JSON;
import com.xiaoluo.lottery.common.enums.OrderStatusEnums;
import com.xiaoluo.lottery.common.enums.PayBuEnums;
import com.xiaoluo.lottery.common.weixin.base.ReturnCode;
import com.xiaoluo.lottery.common.weixin.hb.beans.RedPackRes;
import com.xiaoluo.lottery.common.weixin.msg.beans.TemplateMsgReq;
import com.xiaoluo.lottery.common.weixin.msg.beans.TemplateMsgRes;
import com.xiaoluo.lottery.dao.PlayRecordJdbcDao;
import com.xiaoluo.lottery.dao.RoundJdbcDao;
import com.xiaoluo.lottery.entity.*;
import com.xiaoluo.lottery.exception.LotteryException;
import com.xiaoluo.lottery.exception.OverRoundNumException;
import com.xiaoluo.lottery.request.CreateRoundRequest;
import com.xiaoluo.lottery.request.JoinRoundRequest;
import com.xiaoluo.lottery.vo.CreateRoundResult;
import com.xiaoluo.lottery.vo.RoundVo;
import com.xl.tool.current.DCSCASKit;
import com.xl.tool.current.FaultTolerTask;
import com.xl.tool.redis.JedisResource;
import com.xl.tool.redis.RedisKit;
import com.xl.tool.util.DateUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;

import java.math.BigDecimal;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * Created by Caedmon on 2016/4/5.
 */
@Service
@Transactional
public class RoundServiceImpl implements RoundService{
    @Autowired
    private RoundJdbcDao roundJdbcDao;
    @Autowired
    private PlayRecordJdbcDao playRecordJdbcDao;
    @Autowired
    private PlayerService playerService;
    @Autowired
    private WeiXinServiceImpl weixinService;
    private static final Logger log= LoggerFactory.getLogger(RoundServiceImpl.class);
    private static final String ROUND_LOCK_PREFIX ="round_lock_";
    public static final String ROUND_CACHE_KEY_PREFIX="round_";
    public static final String PLAYER_ONLINE_AGG="player_online_agg_";
    private static final double ROUND_MIN_SINGLE_AMOUNT =0.01;
    private static final int ROUND_MIN_NUM=2;
    private static final ExecutorService delayThreadPool= Executors.newSingleThreadExecutor();
    private static final ThreadLocal<Map<String,AtomicInteger>> threadLocal=new ThreadLocal<>();
    @Override
    public CreateRoundResult createRound(CreateRoundRequest request) {
        if(request.getSingleAmount()< ROUND_MIN_SINGLE_AMOUNT)
            throw LotteryException.build("金额不能低于"+ ROUND_MIN_SINGLE_AMOUNT +"元");
        if(request.getNum()<ROUND_MIN_NUM)
            throw LotteryException.build("人数不能低于"+ROUND_MIN_NUM+"人");
        if(StringUtils.isEmpty(request.getCreatePlayerOpenId()))
            throw LotteryException.build("创建人不能为空");
        Player player=playerService.getPlayer(request.getCreatePlayerOpenId());
        if(player==null) throw LotteryException.build("用户不存在");
        Round round=new Round();
        round.setCreatePlayerOpenId(request.getCreatePlayerOpenId());
        round.setCreateAt(new Date());
        round.setGameType(request.getGameType().value);
        round.setSingleAmount(request.getSingleAmount());
        round.setNum(request.getNum());
        round.setExpiredTime(request.getExpiredTime());
        round.setItemId(request.getItemId());
        round.setName(request.getName());
        round.setRemainNum(request.getNum());
        round.setStatus(Round.STATUS_INIT);
        int i=roundJdbcDao.insert(round);
        if(i!=1){
            log.error("Create round error:createPlayerOpenId={},gameType={},num={},i={}",
                    request.getCreatePlayerOpenId(),request.getGameType(),request.getNum(),i);
            return null;
        }
        //写入缓存
        RedisKit.setex(ROUND_CACHE_KEY_PREFIX+round.getId(),DateUtil.DAY_SECONDS,round);
        //请求加入游戏
        JoinRoundRequest joinRoundRequest=new JoinRoundRequest();
        joinRoundRequest.setIp(request.getIp());
        joinRoundRequest.setOpenId(request.getCreatePlayerOpenId());
        joinRoundRequest.setRoundId(round.getId());
        Order order= getPreJoinPayOrder(joinRoundRequest);
        CreateRoundResult result=new CreateRoundResult(round,order.getPrepayId());
        return result;
    }
    @Override
    public PlayRecord joinRound(JoinRoundRequest request) {
        log.info("Ready join round:roundId={},openId={}",request.getRoundId(),request.getOpenId());
        //统计在线人数用
        RedisKit.setex(PLAYER_ONLINE_AGG+request.getOpenId(),1200,request.getOpenId());
        long roundId=request.getRoundId();
        long start=System.currentTimeMillis();
        DCSCASKit.lock(ROUND_LOCK_PREFIX+roundId,2000L);
        try{
            Round round=getRound(roundId);
            if(round.getStatus()>=Round.STATUS_END)
                throw LotteryException.build("游戏已结束");
            List<PlayRecord> records=round.getRecords();
            //新增一条记录
            PlayRecord record=createPlayRecord(request,round);
            records.add(record);
            round.setRecords(records);
            //playerService.updateBalance(request.getOpenId(),-record.getAmount());
            round.decrRemainNum(record.getNum());
            playRecordJdbcDao.insert(record);
            //如果是第一个人join 则改变游戏状态为running
            if(records.size()==1){
                round.setStatus(Round.STATUS_RUNNING);
            }
            //推送加入游戏消息
            pushJoinRoundMsg(round,record);
            //判断是否可以开奖
            if(round.getRemainNum()==0&&round.getStatus()==Round.STATUS_RUNNING){
                award(round);
            }else{
                //不能开奖则保存到缓存
                RedisKit.setex(ROUND_CACHE_KEY_PREFIX+roundId,DateUtil.DAY_SECONDS,round);
            }


            return record;
        }finally {
            long end=System.currentTimeMillis();
            log.info("耗时:"+(end-start));
            DCSCASKit.unlock(ROUND_LOCK_PREFIX+roundId);
        }


    }
    private PlayRecord createPlayRecord(JoinRoundRequest request,Round round){
        String playerOpenId=request.getOpenId();
        Player player=playerService.getPlayer(playerOpenId);
        if(round.getRemainNum()<request.getNum()){
            throw new OverRoundNumException("roundId="+round.getId()+",remainNum="+round.getRemainNum()+",recordNum="+request.getNum());
        }
        double singleAmount=round.getSingleAmount();
        double shouldPaidAmount=new BigDecimal(singleAmount)
                .multiply(new BigDecimal(request.getNum()))
                .setScale(2,BigDecimal.ROUND_HALF_UP).doubleValue();
        if(request.isUseBalance()){
            if(shouldPaidAmount>player.getBalance()){
                throw LotteryException.build("余额不足");
            }
        }
        final PlayRecord record=new PlayRecord();
        record.setPlayerOpenId(playerOpenId);
        record.setPlayerNickName(player.getNickName());
        //随机指定人次个号码,以逗号分隔
        int currentRoundNum=round.getNum()-round.getRemainNum();
        StringBuilder noBuilder=new StringBuilder();
        for(int i=0;i<request.getNum();i++){
            String playerNO=String.valueOf(currentRoundNum+i+1);
            noBuilder.append(playerNO);
            if(i<request.getNum()-1){
                noBuilder.append(",");
            }
        }
        record.setNum(request.getNum());
        record.setPlayerNo(noBuilder.toString());
        record.setPlayerHeadImage(player.getHeadImage());
        record.setCreateAt(new Date());
        record.setAmount(shouldPaidAmount);
        record.setRoundId(round.getId());
        return record;
    }
    private void submitTask(Runnable task){
        FaultTolerTask faultTask=new FaultTolerTask(task);
        delayThreadPool.execute(faultTask);
    }
    @Override
    public Round award(final Round round) {
        log.info("Ready to award:roundId={}",round.getId());
        //随机号码出来
        List<PlayRecord> records=round.getRecords();
        int winnerNo=new Random().nextInt(round.getNum())+1;
        Player winner=null;
        double totalAmount=0;
        for(PlayRecord record:records){
            if(record.getPlayerNo().contains(String.valueOf(winnerNo))){
                round.setWinnerNo(String.valueOf(winnerNo));
                round.setWinnerOpenId(record.getPlayerOpenId());
                log.info("Round lottery:winnerOpenId={},winnerNo={}",record.getPlayerOpenId(),record.getPlayerNo());
                winner=playerService.getPlayer(record.getPlayerOpenId());
            }
            totalAmount=totalAmount+record.getAmount();
        }
        round.setAwardTime(new Date());
        //playerService.updateBalance(winner.getOpenId(),totalAmount);
        //发奖,微信推送消息并且发红包
        pushAwardMsg(round);
        RedPackRes redPackRes=sendHb(winner,totalAmount,"恭喜你中奖了",round.getName(),"");
        if(!redPackRes.getReturnCode().equals(ReturnCode.SUCCESS)||!redPackRes.getResultCode().equals(ReturnCode.SUCCESS)){
            round.setStatus(Round.STATUS_WAIT_AWARD);
            round.setAwardFailReason(JSON.toJSONString(redPackRes));
        }else{
            round.setStatus(Round.STATUS_END);
        }
        Runnable task=new Runnable() {
                @Override
                public void run() {
                    roundJdbcDao.update(round);
                }
        };
        submitTask(task);
        RedisKit.setex(ROUND_CACHE_KEY_PREFIX+round.getId(),DateUtil.DAY_SECONDS,round);
        return round;
    }

    @Override
    public Round getRound(long roundId) {
        Round round=RedisKit.get(ROUND_CACHE_KEY_PREFIX+roundId);
        if(round==null){
            round=roundJdbcDao.get(roundId);
            if(round==null) throw new LotteryException("游戏不存在");
            List<PlayRecord> records=playRecordJdbcDao.findRecordsByRound(roundId);
            round.setRecords(records);
            RedisKit.setex(ROUND_CACHE_KEY_PREFIX+roundId,DateUtil.DAY_SECONDS,round);
        }
        return round;
    }
    @Override
    public void process(Order order) {
        log.info("Round pay callback:order=",JSON.toJSONString(order));
        String attach=order.getAttach();
        if(StringUtils.isEmpty(attach)){
            throw new NullPointerException("Round pay attach is empty:extendId "+order.getExtendId());
        }
        if(order.getStatus()!=OrderStatusEnums.SUCCESS.getValue()){
            //支付失败
            log.error("Round pay fail:roundId={},openId={}",order.getExtendId(),order.getOpenid());
            return;
        }
        RoundPayAttach payAttach=JSON.parseObject(attach,RoundPayAttach.class);
        Round round=getRound(payAttach.getRoundId());
        //实付金额
        double paidAmount=new BigDecimal(order.getTotalFee())
                .divide(new BigDecimal(100))
                .setScale(2,BigDecimal.ROUND_HALF_UP).doubleValue();
        //应付金额
        double shouldPayAmount=new BigDecimal(round.getSingleAmount())
                .multiply(new BigDecimal(payAttach.getNum()))
                .setScale(2,BigDecimal.ROUND_HALF_UP).doubleValue();
        String openId=order.getOpenid();
        if(paidAmount>=shouldPayAmount){
            JoinRoundRequest request=new JoinRoundRequest();
            request.setOpenId(openId);
            request.setRoundId(round.getId());
            request.setIp(order.getClientIp());

            try{
                joinRound(request);
            }catch (OverRoundNumException e){
                e.printStackTrace();
                playerService.updateBalance(request.getOpenId(),paidAmount);
            }

        }
    }

    @Override
    public Order getPreJoinPayOrder(JoinRoundRequest request) {
        Round round=getRound(request.getRoundId());
        Order order=new Order();
        order.setBody("参与游戏红包");
        order.setDetail("游戏详细说明");
        int totalFee=new BigDecimal(round.getSingleAmount()).
                multiply(new BigDecimal(100)).multiply(new BigDecimal(request.getNum())).intValue();
        order.setTotalFee(totalFee);
        order.setOpenid(request.getOpenId());
        order.setClientIp(request.getIp());
        order.setExtendId(round.getId());
        RoundPayAttach attach=new RoundPayAttach();
        attach.setRoundId(round.getId());
        order.setAttach(JSON.toJSONString(attach));
        return weixinService.doPay(order, PayBuEnums.LOTTERY,this);
    }

    @Override
    public RoundVo getRoundDetail(long roundId) {
        Round round=getRound(roundId);
        RoundVo roundVo=new RoundVo(round);
        Player createPlayer=playerService.getPlayer(round.getCreatePlayerOpenId());
        roundVo.setCreatePlayerNickName(createPlayer.getNickName());
        roundVo.setCreatePlayerHeadImage(createPlayer.getHeadImage());
        roundVo.setTotalAmount(round.getTotalAmount());
        if(!StringUtils.isEmpty(round.getWinnerOpenId())&&(round.getStatus()==Round.STATUS_END||round.getStatus()==Round.STATUS_WAIT_AWARD)){
            Player winner=playerService.getPlayer(round.getWinnerOpenId());
            roundVo.setWinnerNickName(winner.getNickName());
            roundVo.setWinnerHeadImage(winner.getHeadImage());
        }
        return roundVo;
    }
    public void pushAwardMsg(Round round){
        //给中奖人推送的消息
        Set<String> hashPushed=new HashSet<>();
        Map<String,Object> data=new HashMap<>();
        Map<String,Object> first=new HashMap<>();
        first.put("value","游戏中奖通知");
        Map<String,Object> keyword1=new HashMap<>();
        keyword1.put("value","第"+round.getId()+"场游戏,中奖号码"+round.getWinnerNo());
        Map<String,Object> keyword2=new HashMap<>();
        keyword2.put("value", DateUtil.formateDateToString(round.getAwardTime(),"yyyy年MM月dd日HH时mm分ss秒"));
        Map<String,Object> remark=new HashMap<>();
        remark.put("value","恭喜你获得"+round.getId()+"场的游戏总金额"+round.getTotalAmount()+"元");
        data.put("first",first);
        data.put("keyword1",keyword1);
        data.put("keyword2",keyword2);
        data.put("remark",remark);
        TemplateMsgReq winnerMsg=new TemplateMsgReq(round.getWinnerOpenId(),
                "elml-zgLryAzm7ogLpNxrDR8j9f79BzgaPBexg9xt2w","m.xiaoluo.com/game/public?roundId="+round.getId(),data);
        TemplateMsgRes winnerMsgRes= weixinService.sendMsg(winnerMsg);
        log.info("Push wechat msg to winner :response={}",JSON.toJSONString(winnerMsgRes));
        hashPushed.add(round.getWinnerOpenId());
        //给未中奖人推送的消息
        first.put("value","游戏结果通知");
        remark.put("value","很遗憾你未中奖,下次再接再厉");
        data.put("first",first);
        data.put("remark",remark);

        for(PlayRecord record:round.getRecords()){
            if(hashPushed.contains(record.getPlayerOpenId())) continue;
            TemplateMsgReq notAwardMsg=new TemplateMsgReq(record.getPlayerOpenId(),
                    "elml-zgLryAzm7ogLpNxrDR8j9f79BzgaPBexg9xt2w","m.xiaoluo.com/game/public?roundId="+round.getId(),data);
            TemplateMsgRes loseMsgRes= weixinService.sendMsg(notAwardMsg);
            log.info("Push wechat msg to loser :response={}",JSON.toJSONString(loseMsgRes));
            hashPushed.add(record.getPlayerOpenId());
        }
    }
    public void pushJoinRoundMsg(Round round, PlayRecord joinRecord){
        Set<String> hasSendPlayers=new HashSet<>();
            for(PlayRecord tmp:round.getRecords()){
            //如果是自己,则推送不同的信息
            if(hasSendPlayers.contains(tmp.getPlayerOpenId())){
                continue;
            }
            Map<String,Object> data=new HashMap<>();
            Map<String,Object> first=new HashMap<>();
            Map<String,Object> keyword2=new HashMap<>();
            Map<String,Object> keyword1=new HashMap<>();
            Map<String,Object> remark=new HashMap<>();
            int remainNum=round.getRemainNum();
            int currentNum=round.getNum()-round.getRemainNum();
            if(tmp.getPlayerOpenId().equals(joinRecord.getPlayerOpenId())){
                first.put("value","加入游戏成功");
                keyword1.put("value","场次"+round.getId()+",当前人次"+currentNum);
                keyword2.put("value",new SimpleDateFormat("yyyy年MM月dd日HH时mm分ss秒").format(joinRecord.getCreateAt()));
                remark.put("value","您的号码("+joinRecord.getPlayerNo()+"),还差"+remainNum+"人次开奖");
            }else{
                first.put("value",joinRecord.getPlayerNickName()+"加入游戏");
                keyword1.put("value","场次"+round.getId()+",当前人次"+currentNum);
                keyword2.put("value",new SimpleDateFormat("yyyy年MM月dd日HH时mm分ss秒").format(joinRecord.getCreateAt()));
                remark.put("value","他/她的号码("+joinRecord.getPlayerNo()+"),还差"+remainNum+"人次开奖");
            }
            data.put("first",first);
            data.put("keyword1",keyword1);
            data.put("keyword2",keyword2);
            data.put("remark",remark);
            TemplateMsgReq currentPlayerMsg=new TemplateMsgReq(tmp.getPlayerOpenId(),
                    "elml-zgLryAzm7ogLpNxrDR8j9f79BzgaPBexg9xt2w","m.xiaoluo.com/game/public?roundId="+round.getId(),data);
            weixinService.sendMsg(currentPlayerMsg);
            hasSendPlayers.add(tmp.getPlayerOpenId());
        }
    }
    public RedPackRes sendHb(Player winner,double amount,String wishing,String actName,String remark){
        HbInfo hbInfo=new HbInfo();
        //hbInfo.setAmount(new BigDecimal(amount).multiply(new BigDecimal(100)).intValue());
        hbInfo.setTotalAmount(new BigDecimal(amount).multiply(new BigDecimal(100)).intValue());
        hbInfo.setWishing(wishing);
        hbInfo.setActName(actName);
        hbInfo.setRemark(remark);
        hbInfo.setNickName(winner.getNickName());
        hbInfo.setOpenid(winner.getOpenId());
        RedPackRes redPackRes=weixinService.sendHb(hbInfo);
        return redPackRes;
    }
}
